use std::fmt::format;

use bracket_lib::prelude::*;


/* 声明一个枚举，定义游戏模式 */
enum GameMode {
    Menu,
    Playing,
    End,
}

/* 定义游戏中的常量 */
const SCREEN_WIDTH: i32 = 80;
const SCREEN_HEIGHT: i32 = 50;
const FRAME_DURATION: f32 = 50.0;   /* 每隔75ms就刷新一次 */

/* 创建一个游戏主体 */

struct Player{
    x: i32,     /* 世界屏幕的大小 */
    y: i32,
    velocity: f32, /* 采用浮点数，让主体移动变得丝滑 */
}

/* 为Player实现方法 */
impl Player{
    fn new(x: i32, y: i32)->Self{
        Player{
            x: 0,
            y: 0,
            velocity: 0.0,

        }
    }

    /* 渲染 */
    fn render(&mut self, ctx: &mut BTerm){
        ctx.set(0, self.y, YELLOW  , BLACK, to_cp437('@'))
    }
    
    /* 重力下降，以及移动 */
    fn gravity_asnd_move(&mut self){
        if self.velocity < 2.0{
            self.velocity += 0.4;
        }

        self.y += self.velocity as i32;
        self.x += 2;

        if self.y < 0{
            self.y = 0;
        }
    }

    /* 往上飞，坐标原点在左上角 */
    fn flap(&mut self){
        self.velocity = -3.0;
    }

}



/* 创建状态 */
struct State{
    player: Player,
    fram_time: f32,     /* 游戏累计了多少时间，累计了多少帧 */
    mode : GameMode,
    obstacle: Obstacle,
    score: i32,
}

/* 为 State 实现几个方法 */
impl  State{
    /* 创建一个new方法，返回一个结构体 State */
    fn new() -> Self{
        State{
            player: Player::new(5, 25),
            fram_time : 0.0,
            mode: GameMode:: Menu,
            obstacle: Obstacle::new(SCREEN_WIDTH, 0), /* 最初创建的洞是最大的 */
            score: 0,
        }
    
    }
    
    fn restart(&mut self){
        self.player = Player::new(5,25);
        self.fram_time = 0.0;
        self.mode =GameMode::Playing;
        self.obstacle = Obstacle::new(SCREEN_WIDTH, 0);
        self.score = 0;


    }
    
    fn mainmenu(&mut self, ctx: &mut BTerm) {
        ctx.cls();
        ctx.print_centered(5, "welcom to flappy");
        ctx.print_centered(8, "(P) Play Game");
        ctx.print_centered(9, "(Q) Quit Game");

        if let Some(key) = ctx.key{
            match key{
                VirtualKeyCode::P => self.restart(),
                VirtualKeyCode::Q => ctx.quit(),    /* 退出游戏 */
                _ => {}
            }
        }
    }

    fn dead(&mut self, ctx: &mut BTerm) {
        ctx.cls();
        ctx.print_centered(5, "You are dead  ");
        ctx.print_centered(6, &format!("You earned {} points",self.score));
        ctx.print_centered(8, "(P) Play Game");
        ctx.print_centered(9, "(Q) Quit Game");

        if let Some(key) = ctx.key{
            match key{
                VirtualKeyCode::P => self.restart(),
                VirtualKeyCode::Q => ctx.quit() ,
                _ => {}
            }
        }
    }
    
    fn play(&mut self, ctx: &mut BTerm) {
        /* 设置背景颜色 */
       ctx.cls_bg(NAVY);
       /* 获得 ctx的时间值，因为ctx 间隔时间很短，所以要人为的放慢 */
       self.fram_time += ctx.frame_time_ms;
        /* 当每隔 FRAME_DURATION 时间的时候，清空一次时间，并控制游戏主体移动一下 */
        if self.fram_time > FRAME_DURATION{
            self.fram_time = 0.0;
            self.player.gravity_asnd_move();
        }

        /* 按下空格，让player 向上移动 */
        if let Some(VirtualKeyCode::Space) = ctx.key{
            self.player.flap();
        }

        /* 渲染到画面上 */
        self.player.render(ctx);
        ctx.print(0,0, "Press Space to Flap");
        ctx.print(0,1, &format!("Socore:{}",self.score));      

        let mut width_random = RandomNumberGenerator::new();

        self.obstacle.render(ctx, self.player.x );

         /* 如果玩家 的x坐标 越过了 障碍物的坐标，就认为玩家越过去了，得分加一 
             再生成一个障碍，坐标为，当前玩家的坐标加上屏幕的宽度，再把当前得分传进去
        */

        if self.player.x > self.obstacle.x{
            self.score += 1;
         
            self.obstacle = Obstacle::new(self.player.x + SCREEN_WIDTH, self.score);
        }
        

          /* 如果超出高度，也就是坠地了，或者 碰到障碍物了，游戏结束 */
        if self.player.y > SCREEN_HEIGHT || self.obstacle.hit_obstacle(&self.player){
            self.mode = GameMode::End;
        }
       
    
    }

}



/**
 *  为State 实现一个GameState 的方法，进行定时操作
 */
impl GameState for State{

    fn tick(&mut self, ctx: &mut BTerm) {
       match self.mode {
           GameMode::Menu => {
            self.mainmenu(ctx)
        },
           GameMode::Playing => {
            self.play(ctx)
        },
           GameMode::End =>  {
            self.dead(ctx)
        }
   
       } 

        // ctx.cls();  // 清屏，恢复到默认值
        // ctx.print(80, 1, "hello");// 向指定位置打印 output 文字
    }
}


/* 障碍 */
struct Obstacle{
    x: i32,         // 世界空间中的横坐标
    gap_y: i32,
    size: i32,
}

impl Obstacle {
    fn new(x:i32, score: i32) -> Self{

        /* 创建一个随机数 */
        let mut random = RandomNumberGenerator::new();
        /* 初始化一个障碍，设置障碍的 起始点，中间洞的宽度随着得分变小，最小不能小于2 */
        Obstacle{
            x,
            gap_y: random.range(10,40),
            size: i32::max(2, 20 - score),
        }  
    }

    fn render(&mut self, ctx:&mut BTerm, player_x:i32){
        let screen_x = self.x - player_x; /* 需要把障碍的横坐标 - 玩家的横坐标，得到在屏幕中的横坐标 */
        let half_size = self.size / 2;      /* 把中间的洞计算好 */

        /* 渲染 障碍上半截 */
        for y in 0 .. self.gap_y - half_size   {
            ctx.set(screen_x, y, RED,BLACK, to_cp437('|'));
        }

        /* 渲染 障碍下半截 */
        for y in self.gap_y + half_size .. SCREEN_HEIGHT{
            ctx.set(screen_x, y , RED, BLACK, to_cp437('|'));
        }
    }

    fn hit_obstacle(&self, player:&Player) -> bool {
        let half_size = self.size / 2;              /* 计算中间洞的一半大小 */
        let does_x_match = player.x == self.x;     /* 检查 玩家的x坐标是否与障碍的x坐标相等 */
        let player_above_gap = player.y < self.gap_y - half_size; /* 检查 玩家的y坐标 是否小于 障碍物上半截的坐标  */
        let player_below_gap = player.y > self.gap_y + half_size; /* 检查 玩家的y坐标 是否大于于 障碍物下半截的坐标  */
        does_x_match && (player_above_gap || player_below_gap) /* 最后判断，上述三个条件，是否同时满足两个 */
    }


}






fn main() -> BError{
    let context:BTerm = BTermBuilder::simple80x50()  /* 创建一个简单的 80*50的窗口  */
        .with_title("flagppy drangon")      /* 标题上写 flagppy drangon  */
        .build()?;                                       /* 创建，?：表示创建时可能会出错，出错了就立刻返回错误 */
    

    main_loop(context, State::new())  /* 由于main函数是需要返回的，所以这里后边不加分号 */
}
